﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Linq;

namespace CacheSharp.Core.Helpers
{
    /// <summary>
    /// 默认的表格读写实现
    /// </summary>
    internal class DefaultTableHelper : ITableHelper
    {
        #region  Variables and Properties

        /// <summary>
        /// 内部调用的储存器句柄
        /// </summary>
        private ICacher _cacher;

        /// <summary>
        /// 分隔符标识
        /// </summary>
        private readonly string Separator = ":";

        #endregion

        #region Constructors

        public DefaultTableHelper(ICacher cacher, TimeSpan defaultExpireTime)
        {
            Debug.Assert(cacher != null);
            _cacher = cacher;
            DefaultExpireTime = defaultExpireTime;
        }

        #endregion

        #region Implementation of ITableWriter

        public TimeSpan DefaultExpireTime { get; set; }

        public bool SetTable(string groupName, string tableName, string pkName, DataTable table, DateTime expiry)
        {
            if (table == null) return false;
            if (expiry < DateTime.Now) expiry = DateTime.Now + TimeSpan.FromSeconds(1);

            var colNames = (from DataColumn column in table.Columns select column.ColumnName).ToList();
            int count = table.Rows.Count;
            if (count == 0) return true;

            try
            {
                foreach (DataRow row in table.AsEnumerable())
                {
                    var pkValue = row[pkName];
                    Debug.Assert(pkValue != null && !(pkValue is DBNull));
                    foreach (string colName in colNames)
                    {
                        string key = string.Format("{0}{3}{1}{3}{2}", tableName, pkValue, colName, Separator);
                        _cacher.SetValue(groupName, key, row[colName], expiry);
                    }
                }
            }
            catch (Exception)
            {
                return false;
            }
            return true;
        }

        public bool SetTable(string groupName, string tableName, string pkName, DataTable table)
        {
            return SetTable(groupName, tableName, pkName, table, DateTime.Now + DefaultExpireTime);
        }

        public bool SetRow(string groupName, string tableName, string pkName, DataRow row, DateTime expiry)
        {
            if (row == null) return false;
            if (expiry < DateTime.Now) expiry = DateTime.Now + TimeSpan.FromSeconds(1);

            var colNames = (from DataColumn column in row.Table.Columns select column.ColumnName).ToList();
            try
            {
                foreach (string colName in colNames)
                {
                    var pkValue = row[pkName];
                    Debug.Assert(pkValue != null && !(pkValue is DBNull));
                    string key = string.Format("{0}{3}{1}{3}{2}", tableName, pkValue, colName, Separator);
                    _cacher.SetValue(groupName, key, row[colName], expiry);
                }
            }
            catch (Exception)
            {
                return false;
            }
            return true;
        }

        public bool SetRow(string groupName, string tableName, string pkName, DataRow row)
        {
            return SetRow(groupName, tableName, pkName, row, DateTime.Now + DefaultExpireTime);
        }

        public bool SetRows(string groupName, string tableName, string pkName, DataRow[] rows, DateTime expiry)
        {
            if (rows == null || rows.Length == 0) return false;
            if (expiry < DateTime.Now) expiry = DateTime.Now + TimeSpan.FromSeconds(1);

            var colNames = (from DataColumn column in rows[0].Table.Columns select column.ColumnName).ToList();
            try
            {
                foreach (DataRow row in rows)
                {
                    if (row == null) continue;

                    foreach (string colName in colNames)
                    {
                        var pkValue = row[pkName];
                        Debug.Assert(pkValue != null && !(pkValue is DBNull));
                        string key = string.Format("{0}{3}{1}{3}{2}", tableName, pkValue, colName, Separator);
                        _cacher.SetValue(groupName, key, row[colName], expiry);
                    }
                }
            }
            catch (Exception)
            {
                return false;
            }
            return true;
        }

        public bool SetRows(string groupName, string tableName, string pkName, DataRow[] rows)
        {
            return SetRows(groupName, tableName, pkName, rows, DateTime.Now + DefaultExpireTime);
        }

        /// <summary>
        /// 判断key是否为表中键值
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        private bool IsTableKey(string key)
        {
            if (string.IsNullOrEmpty(key)) return false;
            string keyTmp = key.Replace(Separator, string.Empty);
            return (key.Length - keyTmp.Length == 2) && (!key.EndsWith(Separator));
        }

        public bool RemoveRow(string groupName, string tableName, object pkValue)
        {
            Debug.Assert(groupName != null && tableName != null && pkValue != null && !(pkValue is DBNull));

            try
            {
                string keyStart = tableName + Separator + pkValue + Separator;
                var keys = _cacher.GetValues(groupName, keyStart).Keys.ToArray();
                foreach (string key in keys)
                {
                    if (IsTableKey(key)) _cacher.Remove(groupName, key);
                }

            }
            catch (Exception)
            {
                return false;
            }
            return true;
        }

        public bool RemoveTable(string groupName, string tableName)
        {
            Debug.Assert(groupName != null && tableName != null);

            try
            {
                string keyStart = tableName + Separator;
                var keys = _cacher.GetValues(groupName, keyStart).Keys.ToArray();
                foreach (string key in keys)
                {
                    if (IsTableKey(key)) _cacher.Remove(groupName, key);
                }

            }
            catch (Exception)
            {
                return false;
            }
            return true;
        }

        public bool RemoveGroup(string groupName)
        {
            return _cacher.RemoveGroup(groupName);
        }

        public bool RemoveAll()
        {
            return _cacher.RemoveAll();
        }

        #endregion

        #region Implementation of ITableReader

        public bool ExistTable(string groupName, string tableName)
        {
            Debug.Assert(groupName != null && tableName != null);
            return _cacher.CachedNum(groupName) != 0;
        }

        public bool ExistColumn(string groupName, string tableName, string colName)
        {
            Debug.Assert(groupName != null && tableName != null && colName != null);
            if (!ExistTable(groupName, tableName)) return false;

            string keyStart = tableName + Separator;
            var keys = _cacher.GetValues(groupName, keyStart).Keys.ToArray().Where(IsTableKey);

            return
                (from key in keys
                    let index = key.LastIndexOf(Separator, StringComparison.Ordinal)
                    select key.Substring(index + 1)).Any(name => name.Equals(colName));
        }


        public List<string> GetExistColumnNames(string groupName, string tableName)
        {
            Debug.Assert(groupName != null && tableName != null);
            if (!ExistTable(groupName, tableName)) return new List<string>();

            string keyStart = tableName + Separator;
            var keys = _cacher.GetValues(groupName, keyStart).Keys.ToArray().Where(IsTableKey);
            List<string> list = new List<string>();
            foreach (string key in keys)
            {
                int index = key.LastIndexOf(Separator, StringComparison.Ordinal);
                string name = key.Substring(index + 1);
                if (!list.Contains(name)) list.Add(name);
            }
            return list;
        }

        public DateTime? GetTableExpireTime(string groupName, string tableName)
        {
            Debug.Assert(groupName != null && tableName != null);
            if (!ExistTable(groupName, tableName)) return null;

            string keyStart = tableName + Separator;
            var keys = _cacher.GetValues(groupName, keyStart).Keys.ToArray().Where(IsTableKey);
            List<DateTime> list =
                keys.Select(key => _cacher.GetExpireTime(groupName, key))
                    .Where(dt => dt != null)
                    .Select(dt => (DateTime) dt)
                    .ToList();
            if (list.Count == 0) return null;
            return list.Min();
        }

        /// <summary>
        /// 制作空数据表
        /// </summary>
        /// <param name="tableName"></param>
        /// <param name="colNames"></param>
        /// <returns></returns>
        private DataTable MakeEmptyTable(string tableName, IEnumerable<string> colNames)
        {
            Debug.Assert(tableName != null && colNames != null);
            DataTable table = new DataTable(tableName);
            foreach (string colName in colNames)
            {
                table.Columns.Add(colName);
            }
            return table;
        }

        /// <summary>
        /// 得到单数据表中的主键列表
        /// </summary>
        /// <param name="keys"></param>
        /// <returns></returns>
        private List<string> GetPkValues(IEnumerable<string> keys)
        {
            string[] separators = {Separator};
            List<string> list = new List<string>();
            foreach (string key in keys)
            {
                var splits = key.Split(separators, StringSplitOptions.None);
                if (!list.Contains(splits[1])) list.Add(splits[1]);
            }
            return list;
        }


        private DataTable GetOrPopTable(string groupName, string tableName, bool pop)
        {
            Debug.Assert(groupName != null && tableName != null);
            if (!ExistTable(groupName, tableName)) return null;

            var colNames = GetExistColumnNames(groupName, tableName);
            var table = MakeEmptyTable(tableName, colNames);

            string keyStart = tableName + Separator;
            var keys = _cacher.GetValues(groupName, keyStart).Keys.ToArray().Where(IsTableKey);
            var pkValues = GetPkValues(keys);

            foreach (string pkValue in pkValues)
            {
                DataRow row = table.NewRow();
                foreach (string colName in colNames)
                {
                    string key = string.Format("{0}{3}{1}{3}{2}", tableName, pkValue, colName, Separator);
                    var value = _cacher.GetValue(groupName, key);
                    row[colName] = value;
                    if (pop) _cacher.Remove(groupName, key);
                }
                table.Rows.Add(row);
            }
            table.AcceptChanges();
            return table;
        }

        public DataTable GetTable(string groupName, string tableName)
        {
            return GetOrPopTable(groupName, tableName, false);
        }

        public DataRow GetRow(string groupName, string tableName, string pkName, object pkValue)
        {
            var table = GetTable(groupName, tableName);
            if (table == null) return null;

            string strPkValue = pkValue.ToString();
            return table.AsEnumerable().FirstOrDefault(row => row[pkName].ToString().Equals(strPkValue));
        }

        public int GetRowNum(string groupName, string tableName)
        {
            var table = GetTable(groupName, tableName);
            if (table == null) return 0;

            return table.Rows.Count;
        }

        public object GetValue(string groupName, string tableName, object pkValue, string destColName)
        {
            Debug.Assert(groupName != null && tableName != null);
            Debug.Assert(pkValue != null && destColName != null);
            if (!ExistTable(groupName, tableName)) return null;

            string key = tableName + Separator + pkValue + Separator + destColName;
            return _cacher.GetValue(groupName, key);
        }

        public object GetValue(string groupName, string tableName, string expression)
        {
            var table = GetTable(groupName, tableName);
            if (table == null) return null;

            return table.Select(expression);
        }

        public DataTable PopTable(string groupName, string tableName)
        {
            return GetOrPopTable(groupName, tableName, true);
        }

        #endregion

        #region Implementation of ITableHelper

        public string TableHelperName
        {
            get { return "DefaultTableHelper"; }
        }

        #endregion
    }
}
